package com.idreamsky.core.lib.util;

import greendroid.util.GDUtils;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.ref.SoftReference;
import java.net.URI;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import android.app.Activity;
import android.graphics.Bitmap;
import android.util.Log;

import com.idreamsky.core.lib.http.ClientHttpRequest;
import com.idreamsky.core.lib.http.IRequestListener;

public class AsyncImageLoader {
    private static final String TAG = AsyncImageLoader.class.getSimpleName();

    public static final ConcurrentHashMap<String, SoftReference<Bitmap>> mBitmapPool = new ConcurrentHashMap<String, SoftReference<Bitmap>>();
    private static final HashMap<String, LinkedList<Callback>> mImagePool = new HashMap<String, LinkedList<Callback>>();
    private static final Object SNYC = new Object();

    public Bitmap load(String url, Callback cb) {

        return load(url, cb, false);
    }

    public Bitmap load(final String url, final Callback cb,
            final boolean isLarge) {
        Bitmap bitmap = null;
        // find from pool
        if (mBitmapPool.containsKey(url)) {
            bitmap = mBitmapPool.get(url).get();
            if (bitmap != null) {
                return bitmap;
            }
            mBitmapPool.remove(url);
        }
        
        // find from local
        final String path = makeNetworkFilePath(url);

        if (path != null) {
            bitmap = BitmapUtil.getBitmapFromPath(path, isLarge);
        }
        if (bitmap != null) {
            mBitmapPool.put(url, new SoftReference<Bitmap>(bitmap));
            return bitmap;
        }

        synchronized (SNYC) {
            boolean flag = mImagePool.containsKey(url);
            LinkedList<Callback> callbacks = mImagePool.get(url);
            if (callbacks == null) {
                callbacks = new LinkedList<Callback>();
                mImagePool.put(url, callbacks);
            }
            callbacks.add(cb);
            if (flag) {
                return null;
            }
        }
        
        final IRequestListener listener = new IRequestListener() {
            @Override
            public void onFailed(String msg, String state) {
                onFinish(url, state);
            }
            @Override
            public void onComplete(InputStream is, int length, String state) {
                Bitmap bitmap = null;
                try {
                    if (onSave(is, path, length, state)) {
                        bitmap = BitmapUtil.getBitmapFromPath(path, isLarge);
                    } else {
                        bitmap = getBitmapFromInputStream(is, isLarge, length, state);
                    }
                    if (is != null) {
                        is.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }

                if (null != bitmap) {
                    onFinish((String)state, bitmap);
                    mBitmapPool.put((String)state, new SoftReference<Bitmap>(bitmap));
                } else {
                    onFinish((String)state, "Can not decode " + state + " as a bitmap.");
                }
            }
        };
        ThreadPool.getInstance().getPoolExecutor().execute(new Runnable() {
            @Override
            public void run() {
                ClientHttpRequest.openUrl(url, ClientHttpRequest.GET, null, listener);
            }
        });
        
        return bitmap;
    }

    private static void onFinish(String url, Object obj) {
        synchronized (SNYC) {
            LinkedList<Callback> callbacks = mImagePool.get(url);
            if (callbacks != null) {
                for (Callback callback : callbacks) {
                    if (callback != null) {
                        if (obj instanceof Bitmap) {
                            callback.onSuccess((Bitmap) obj, url);
                        } else {
                            callback.onFail((String) obj, url);
                        }
                    }
                }
                mImagePool.remove(url);
            }
        }
    }

    private boolean onSave(InputStream is, String fileName,
            long fileLength, String url) {
        if (fileLength <= 0) {
            return false;
        }
        String path = GDUtils.getCachePath();
        if (path != null) {
            File filePath = new File(path);
            if (!filePath.exists() && !filePath.mkdirs()) {
                return false;
            }
        } else {
            return false;
        }
        String temFile = fileName + ".tmp";
        OutputStream outStream = null;
        boolean flag = false;
        long downLength = 0;
        try {
            outStream = new FileOutputStream(temFile);
            final byte[] buffer = new byte[8192];
            while (true) {
                final int size = is.read(buffer);
                if (size <= 0) {
                    break;
                }
                downLength += size;
                synchronized (SNYC) {
                    LinkedList<Callback> callbacks = mImagePool.get(url);
                    if (callbacks != null) {
                        for (Callback callback : callbacks) {
                            if (callback != null) {
                                callback.onProgress(
                                        (int) ((downLength * 100) / (fileLength)),
                                        url);
                            }
                        }
                    }
                }
                outStream.write(buffer, 0, size);
            }
            outStream.flush();
            File file = new File(temFile);
            file.renameTo(new File(fileName));
            flag = true;
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            flag = false;
        } catch (IOException e) {
            e.printStackTrace();
            flag = false;
        } finally {
            try {
                if (outStream != null)
                    outStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return flag;
    }

    public interface Callback {
        void onSuccess(Bitmap bitmap, String url);

        void onFail(String msg, String url);

        void onProgress(int i, String url);
    }

    public static void recycle() {
        if (mBitmapPool != null) {
            mBitmapPool.clear();
        }
    }

    private static Bitmap getBitmapFromInputStream(InputStream is, boolean isLarge, long fileLength, String url) {
        ByteArrayOutputStream bao = new ByteArrayOutputStream();
        final byte[] buffer = new byte[8192];
        int downLength = 0;
        byte[] dataByte = null;
        try {
            while (true) {
                final int size = is.read(buffer);
                if (size <= 0) {
                    break;
                }
                downLength += size;
                synchronized (SNYC) {
                    LinkedList<Callback> callbacks = mImagePool.get(url);
                    if (callbacks != null) {
                        for (Callback callback : callbacks) {
                            if (callback != null) {
                                callback.onProgress(
                                        (int) ((downLength * 100) / (fileLength)),
                                        url);
                            }
                        }
                    }
                }
                bao.write(buffer, 0, size);
            }
            bao.flush();
            dataByte = bao.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                bao.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if (dataByte == null) {
            return null;
        }
        Bitmap bmp = BitmapUtil.getBitmapFromBytes(dataByte, isLarge);
        return bmp;
    }

    public static void addBitmap(String path, Bitmap bitmap) {
        mBitmapPool.put(path, new SoftReference<Bitmap>(bitmap));
    }


    public static Bitmap loadFromLocal(final Activity activity, final String path, final int px, final Callback cb) {
        Bitmap bitmap = null;
        // find from pool
        if (mBitmapPool.containsKey(path + px)) {
            bitmap = mBitmapPool.get(path + px).get();
            if (bitmap != null) {
                return bitmap;
            }
            mBitmapPool.remove(path + px);
        }
        
        synchronized (SNYC) {
            boolean flag = mImagePool.containsKey(path);
            LinkedList<Callback> callbacks = mImagePool.get(path);
            if (callbacks == null) {
                callbacks = new LinkedList<Callback>();
                mImagePool.put(path, callbacks);
            }
            callbacks.add(cb);
            if (flag) {
                return null;
            }
        }
        
        ThreadPool.getInstance().getPoolExecutor().execute(new Runnable() {
            @Override
            public void run() {
                Bitmap bitmap = FileUtil.getThumbnail(activity, path);
                if (bitmap == null) {
                    bitmap = BitmapUtil.getBitmapFromPath(path, px);
                }
                if (bitmap != null) {
                    mBitmapPool.put(path + px, new SoftReference<Bitmap>(bitmap));
                    onFinish(path, bitmap);
                } else {
                    onFinish(path, "get bitmap failed");
                }
            }
        });
        
        return bitmap;
    }
    
    /**
     * 释放bitmap
     * @param key
     */
    public static void releaseBitmap(String key) {
        SoftReference<Bitmap> delBitmap = mBitmapPool.get(key);
        if (delBitmap != null) {
            Bitmap bitmap = delBitmap.get();
            if (bitmap != null && !bitmap.isRecycled()) {
                bitmap.recycle();
                bitmap = null;
            }
            mBitmapPool.remove(key);
        } else {
            mBitmapPool.remove(key);
        }
    }
    
    private static final String TOESCAPE = "<>:\"|?*\\";

    public String makeNetworkFilePath(String url) {
        String cachePath = GDUtils.getCachePath();
        if (cachePath == null) {
            return null;
        }
        URI uri;
        try {
            uri = new URI(url);
        } catch (java.net.URISyntaxException ex) {
            return null;
        }

        String host = uri.getHost();

        StringBuilder path = new StringBuilder(host);
        if (host.startsWith("www.")) {
            path.delete(0, 4);
        }
        path.insert(0, File.separator);
        path.insert(0, cachePath);

        int index = path.length();

        path.append(uri.getPath());

        int nameIndex = index;
        while (index < path.length()) {
            char ch = path.charAt(index);
            if (TOESCAPE.indexOf(ch) != -1) {
                path.setCharAt(index, '_');
            }
            if (ch == '/') {
                if (index + 1 == path.length()) {
                    path.deleteCharAt(index);
                } else {
                    path.setCharAt(index, '_');
                    nameIndex = index + 1;
                }
            }
            ++index;
        }

        String ext = null;
        int j = path.lastIndexOf(".");
        if (j > nameIndex) {
            ext = path.substring(j);
            path.delete(j, path.length());
        } else {
            ext = "";
        }

        String query = uri.getQuery();
        if (query != null) {
            index = 0;
            while (index < query.length()) {
                j = query.indexOf("&", index);
                if (j == -1) {
                    j = query.length();
                }
                String param = query.substring(index, j);
                if (!param.startsWith("username=")
                    && !param.startsWith("password=")
                    && !param.endsWith("=")) {
                    int k = path.length();
                    path.append("_").append(param);
                    while (k < path.length()) {
                        char ch = path.charAt(k);
                        if (TOESCAPE.indexOf(ch) != -1 || ch == '/') {
                            path.setCharAt(k, '_');
                        }
                        ++k;
                    }
                }
                index = j + 1;
            }
        }
        return path.append(ext).toString();
    }
    
    public static class ThreadPool {
        private RequestPoolExecutor mExecutor;

        private static final int CORE_POOL_SIZE = 3;

        private static final int MAX_POOL_SIZE = 4;

        private static final int ALIVE_TIME = 30;

        private static ThreadPool sInstance;

        private static byte[] SYNC = new byte[0];

        public static ThreadPool getInstance() {
            if (null == sInstance) {
                synchronized (SYNC) {
                    if (null == sInstance) {
                        sInstance = new ThreadPool();
                    }
                }
            }
            return sInstance;
        }

        private ThreadPool() {
            mExecutor = new RequestPoolExecutor();
        }

        public ThreadPoolExecutor getPoolExecutor() {
            return mExecutor;
        }

        private static class RequestPoolExecutor extends ThreadPoolExecutor {

            public RequestPoolExecutor() {
                super(CORE_POOL_SIZE, MAX_POOL_SIZE, ALIVE_TIME, TimeUnit.SECONDS,
                        new LinkedBlockingQueue<Runnable>(),
                        new RejectedExecutionHandler() {

                            @Override
                            public void rejectedExecution(Runnable r,
                                    ThreadPoolExecutor executor) {
                                Log.e(TAG, "Reject executing runnable " + r.toString());
                            }
                        });
            }
        }
    }
}
